package com.zyk.script.manager.plugins;

import cn.hutool.core.thread.NamedThreadFactory;
import com.zyk.script.manager.plugin.IScriptPlugin;
import com.zaxxer.hikari.HikariDataSource;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.jdbc.core.JdbcTemplate;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

/**
 * Hello world!
 */
public class Csv2DbPlugin implements IScriptPlugin {
    private final Logger logger = LoggerFactory.getLogger(Csv2DbPlugin.class);

    /**
     * CSV 期待的列数，不够使用null
     */
    private Integer colSum;

    /**
     * CSV 忽略的行数(忽略header)
     */
    private Integer ignoreNum;

    /**
     * 插入数据库每批次大小
     */
    private Integer bathSize = 800;

    /**
     * sql语句freemark模板
     */
    private String sqltemplate;

    /**
     * csv地址
     */
    private String pathStr;

    /**
     * 数据源配置
     */
    private DataSourceProperties dataSourceProperties;

    /**
     * 数据源
     */
    private HikariDataSource dataSource;
    private JdbcTemplate jdbcTemplate;

    /**
     * 插入执行线程池
     */
    private ExecutorService execute;

    /**
     * freemark模板
     */
    private Template template;

    @Override
    public void run(String[] args, Properties properties) throws InterruptedException, IOException {
        List<String> errorSqls = new ArrayList<>();
        try {
            // 初始化参数、数据库
            init(args, properties);
            // 批量插入
            productSql(sqls -> {
                execute.execute(() -> {
                    try {
                        jdbcTemplate.batchUpdate(sqls);
                        logger.info("[" + Thread.currentThread().getName() + "] - 执行完1批次" + sqls.length + "的数据");
                    } catch (Exception e) {
                        errorSqls.addAll(Arrays.asList(sqls));
                        e.printStackTrace();
                    }
                });
            });

            execute.shutdown();
            while (!execute.isTerminated()) {
                Thread.sleep(50);
            }
            logger.info("-------------------------------------------------------------------------");
            logger.info("完成");
        } finally {
            if (dataSource != null) {
                dataSource.close();
            }
            if (execute != null) {
                execute.shutdown();
            }
            logger.info("-------------------------------------------------------------------------");
            logger.info("错误sql:");
            for (String errorSql : errorSqls) {
                logger.info(errorSql);
            }
        }
    }

    private void productSql(Consumer<String[]> sqlConsumer) {
        Path path = Paths.get(pathStr);
        long i = 0;
        try (BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
            List<String> result = new ArrayList<>();
            for (; ; ) {
                String field = reader.readLine();
                if (ignoreNum > 0) {
                    ignoreNum--;
                    continue;
                }
                if (field == null) {
                    break;
                }
                field = field.trim();
                if (field.isEmpty()) {
                    continue;
                }
                String[] split = field.trim().split(",");
                String sql = freemarkerToSql(split);
                if (sql == null || sql.isEmpty()) {
                    continue;
                }
                result.add(sql);
                i++;
                if (i >= bathSize) {
                    sqlConsumer.accept(result.stream().toArray(String[]::new));
                    result.clear();
                    i = 0;
                }
            }
            if (!result.isEmpty()) {
                sqlConsumer.accept(result.stream().toArray(String[]::new));
                result.clear();
            }
        } catch (IOException | TemplateException e) {
            e.printStackTrace();
        }
    }

    private String freemarkerToSql(String[] split) throws IOException, TemplateException {
        HashMap<String, String> map = new HashMap<>();
        String tmp;
        for (int i = 1; i <= colSum; i++) {
            if (i <= split.length) {
                tmp = split[i - 1];
                if (StringUtils.isBlank(tmp)) {
                    tmp = "NULL";
                }
                map.put("col" + i, tmp.trim());
            } else {
                map.put("col" + i, "NULL");
            }
        }
        if (map.isEmpty()) {
            return null;
        }
        StringWriter writer = new StringWriter();
        template.process(map, writer);
        return writer.toString();
    }

    private void init(String[] args, Properties properties) throws IOException {
        sqltemplate = properties.getProperty("script.plugins.csv2db.sqltemplate");
        String bathSizeStr = properties.getProperty("script.plugins.csv2db.bathSize", "" + bathSize);
        bathSize = Integer.valueOf(bathSizeStr);

        String ignoreNumStr = properties.getProperty("script.plugins.csv2db.ignoreNum", "0");
        ignoreNum = Integer.valueOf(ignoreNumStr);

        String colSumStr = properties.getProperty("script.plugins.csv2db.colSum", "1");
        colSum = Integer.valueOf(colSumStr);
        if (colSum < 1) {
            colSum = 1;
        }

        pathStr = properties.getProperty("script.plugins.csv2db.pathStr");

        String url = properties.getProperty("script.plugins.csv2db.dataSource.url");
        String name = properties.getProperty("script.plugins.csv2db.dataSource.name");
        String password = properties.getProperty("script.plugins.csv2db.dataSource.password");
        String driver = properties.getProperty("script.plugins.csv2db.dataSource.driver");
        dataSourceProperties = new DataSourceProperties();
        dataSourceProperties.setUrl(url);
        dataSourceProperties.setUsername(name);
        dataSourceProperties.setPassword(password);
        dataSourceProperties.setDriverClassName(driver);

        dataSource = dataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
        jdbcTemplate = new JdbcTemplate(dataSource);
        execute = new ThreadPoolExecutor(
                Integer.valueOf(properties.getProperty("script.plugins.csv2db.pool.coreSize", "8")),
                Integer.valueOf(properties.getProperty("script.plugins.csv2db.pool.maximumSize", "8")),
                0L,
                TimeUnit.MILLISECONDS,
                new SynchronousQueue(),
                new NamedThreadFactory(properties.getProperty("script.plugins.csv2db.pool.threadNamed", "big-data-create"), true),
                (r, executor) -> {
                    try {
                        // 阻塞主线程
                        executor.getQueue().put(r);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                });
        // 第一步：创建一个Configuration对象，直接new一个对象。构造方法的参数就是FreeMarker对于的版本号。
        Configuration configuration = new Configuration(Configuration.getVersion());
        // 第二步：设置模板文件所在的目录。
        configuration.setDirectoryForTemplateLoading(new File(properties.getProperty("script.plugins.csv2db.ftldir")));
        // 第三步：设置模板文件使用的字符集。一般就是utf-8.
        configuration.setDefaultEncoding("utf-8");
        // 第四步：加载一个模板，创建一个模板对象。
        template = configuration.getTemplate(sqltemplate);
        // template = new Template("name", new StringReader(sqltemplate), new Configuration(Configuration.getVersion()));
    }

    @Override
    public String getName() {
        return "Csv2DbPlugin";
    }
}
